// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include "CondorSubmit.hpp"
#include "Exception.hpp"
#include "Global.hpp"

using namespace ninf::client;

const char * condor_universe_array[] = {
  "standard",
  "vanilla",
  "globus"
};


CondorSubmit :: CondorSubmit(
  const char * module,
  const char * entry,
  int          pid,
  int          id )
  : pid(pid), id(id),
    module(strdup(module)), entry(strdup(entry)), stubs()
{
}


CondorSubmit :: ~CondorSubmit(){

  for (unsigned int i = 0; i < stubs.size(); i++){
    free(stubs[i]);
  }
  free((char *)module);
  free((char *)entry);
}


void
CondorSubmit :: collectBinaries(){
  DIR * dir;
  char buffer[1000];
  struct dirent * tdirent;
  struct stat tstat;
  const char * name = ".";

  if ((dir = opendir(name)) == NULL){
    fprintf(stderr, "can't open dir: %s\n", name);
    return;
  }
  char stubname[1000];
  sprintf(stubname, "_stub_%s_%s", module, entry);

  printf("here\n");

  while ((tdirent = readdir(dir)) != NULL){
    if (strncmp(tdirent->d_name, stubname, strlen(stubname)) == 0) {
      strncpy(buffer, name, 1000 - strlen(tdirent->d_name) - 2);
      strcat(buffer, "/");
      strcat(buffer, tdirent->d_name);
      stat(buffer, &tstat);
      if ((tstat.st_mode & S_IXUSR) != 0){
	stubs.push_back(strdup(buffer));
      }
    }
  }
  closedir(dir);
}

void
CondorSubmit :: writeRequirement(FILE * fp, const char * execName){
  char * tmp = strdup(execName);
  strtok(tmp, ".");
  char * opName = strtok(NULL, ".");
  char * arcName = strtok(NULL, ".");
  fprintf(fp, "\t(Arch == \"%s\" && OpSys == \"%s\") ",
	  arcName, opName);
}

void
CondorSubmit :: createSubmitFile(const char * filename,
				   vector <const char *> & stageIn,
				   vector <const char *> & stageOut){
  collectBinaries();
  if (stubs.size() == 0)
    EXCEPTION("no executable found");

  FILE * fp = fopen(filename, "w");

  fprintf(fp, "Universe        = %s\n", condor_universe_array[Global::global.universe]);
  fprintf(fp, "Executable      = _stub_%s_%s.$$(OpSys).$$(Arch)\n",
	  module, entry);

  fprintf(fp, "Requirements    = ( \\\n");
  unsigned int i;
  for (i = 0; i < stubs.size() - 1; i++){
    writeRequirement(fp, stubs[i]);
    fprintf(fp, " || \\\n");
  }
  writeRequirement(fp, stubs[i]);
  fprintf(fp, "\\\n");
  fprintf(fp, ")\n");

  if (Global::global.universe == UNIVERSE_VANILLA ||
      Global::global.universe == UNIVERSE_GLOBUS){
    fprintf(fp, "Arguments       ="
        " -inputfile "  TO_FF
        " -outputfile " FROM_FF
        " -metafile "   META_FF "\n",
	    id, id, id);

    fprintf(fp, "InitialDir = %s\n", Global::global.runDir);

//    fprintf(fp, "transfer_input_files = " TO_FF , id);
    fprintf(fp, "transfer_input_files = ");
    for (unsigned i = 0; i < stageIn.size(); i++)
      fprintf(fp, ",%s", stageIn[i]);
    fprintf(fp, "\n");

  //    fprintf(fp, "transfer_output_files = " FROM_FF , id);
    //  these are not required and can be error prone.
    //    for (unsigned i = 0; i < stageOut.size(); i++)
    //      fprintf(fp, ",%s", stageOut[i]);
    fprintf(fp, "\n");

    fprintf(fp, "WhenToTransferOutput = ON_EXIT\n");

    fprintf(fp, "output = %d.out\n", id);
    fprintf(fp, "error = %d.err\n", id);
    fprintf(fp, "log = log\n");

  } else {
    /********************** for standard ******************************/

    fprintf(fp, "Arguments       = -inputfile %s/" TO_FF
	                         " -outputfile %s/" FROM_FF "\n",
    	    Global::global.runDir, id,
	    Global::global.runDir, id);

    fprintf(fp, "output = %s/%d.out\n", Global::global.runDir, id);
    fprintf(fp, "error = %s/%d.err\n", Global::global.runDir, id);
    fprintf(fp, "log = %s/log\n", Global::global.runDir);
  }


  fprintf(fp, "Queue\n");
  fclose(fp);
}

bool
CondorSubmit :: checkFileExistance(const char * filename){
  FILE * fp = fopen(filename, "r");
  if (fp != NULL){
    fclose(fp);
    return true;
  }
  return false;
}

int
CondorSubmit :: submit(vector <const char *> & stageIn,
			 vector <const char *> & stageOut){
  char submitFile[1000];

  // throttling
  Global::global.sessionManager.throttling();

  sprintf(submitFile, "%s/%d.submit", Global::global.runDir, id);

  // if the file already exists, skip all the submission process
  // to reuse the result
  if (! checkFileExistance(submitFile)) {

    createSubmitFile(submitFile, stageIn, stageOut);

    char buffer[2000];

    if (Global::global.condorBinDir != NULL)
      sprintf(buffer, "%s/%s %s",
	      Global::global.condorBinDir, CONDOR_SUBMIT, submitFile);
    else
      sprintf(buffer, "%s %s",
	      CONDOR_SUBMIT, submitFile);

    // log
    fprintf(stderr, "%s\n", buffer);

    // invoke and open the stream

    if (system(buffer) == 1){
      perror("system");
      EXCEPTION("failed to 'system'");
    }
  }

  return Global::global.sessionManager.getSubmittedJobId();
}


void
CondorSubmit :: cancel(int condorID){
  char buffer[2000];

  if (Global::global.condorBinDir != NULL)
    sprintf(buffer, "%s/%s %d",
	    Global::global.condorBinDir, CONDOR_RM, condorID);
  else
    sprintf(buffer, "%s %d",
	    CONDOR_RM, condorID);

  if (system(buffer) == 1){
    perror("system");
    EXCEPTION("failed to 'system'");
  }
}
